﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FileSystemStorageProvider.cs" company="">
//   
// </copyright>
// <summary>
//   The file system storage provider.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Skymate.Core.IO.Media
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web.Hosting;

    using Skymate.Utilities;

    /// <summary>
    /// The file system storage provider.
    /// </summary>
    public class FileSystemStorageProvider : IStorageProvider
    {
        /// <summary>
        /// The _public path.
        /// </summary>
        private readonly string _publicPath;

        /// <summary>
        /// The _storage path.
        /// </summary>
        private readonly string _storagePath;

        /// <summary>
        /// Initializes a new instance of the <see cref="FileSystemStorageProvider"/> class.
        /// </summary>
        /// <param name="settings">
        /// The settings.
        /// </param>
        public FileSystemStorageProvider(FileSystemSettings settings)
        {
            var mediaPath = CommonHelper.MapPath("~/Media/", false);
            this._storagePath = Path.Combine(mediaPath, settings.DirectoryName);

            var appPath = string.Empty;
            if (HostingEnvironment.IsHosted)
            {
                appPath = HostingEnvironment.ApplicationVirtualPath;
            }

            if (!appPath.EndsWith("/"))
            {
                appPath = appPath + '/';
            }

            if (!appPath.StartsWith("/"))
            {
                appPath = '/' + appPath;
            }

            this._publicPath = appPath + "Media/" + settings.DirectoryName + "/";
        }

        /// <summary>
        /// The map.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private string Map(string path)
        {
            return string.IsNullOrEmpty(path) ? this._storagePath : Path.Combine(this._storagePath, path);
        }

        /// <summary>
        /// The fix.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private static string Fix(string path)
        {
            return string.IsNullOrEmpty(path)
                       ? string.Empty
                       : Path.DirectorySeparatorChar != '/' ? path.Replace('/', Path.DirectorySeparatorChar) : path;
        }

        /// <summary>
        /// The file system storage file.
        /// </summary>
        private class FileSystemStorageFile : IStorageFile
        {
            /// <summary>
            /// The _file info.
            /// </summary>
            private readonly FileInfo _fileInfo;

            /// <summary>
            /// The _path.
            /// </summary>
            private readonly string _path;

            /// <summary>
            /// Initializes a new instance of the <see cref="FileSystemStorageFile"/> class.
            /// </summary>
            /// <param name="path">
            /// The path.
            /// </param>
            /// <param name="fileInfo">
            /// The file info.
            /// </param>
            public FileSystemStorageFile(string path, FileInfo fileInfo)
            {
                this._path = path;
                this._fileInfo = fileInfo;
            }

            #region Implementation of IStorageFile

            /// <summary>
            /// The get path.
            /// </summary>
            /// <returns>
            /// The <see cref="string"/>.
            /// </returns>
            public string GetPath()
            {
                return this._path;
            }

            /// <summary>
            /// The get name.
            /// </summary>
            /// <returns>
            /// The <see cref="string"/>.
            /// </returns>
            public string GetName()
            {
                return this._fileInfo.Name;
            }

            /// <summary>
            /// The get size.
            /// </summary>
            /// <returns>
            /// The <see cref="long"/>.
            /// </returns>
            public long GetSize()
            {
                return this._fileInfo.Length;
            }

            /// <summary>
            /// The get last updated.
            /// </summary>
            /// <returns>
            /// The <see cref="DateTime"/>.
            /// </returns>
            public DateTime GetLastUpdated()
            {
                return this._fileInfo.LastWriteTime;
            }

            /// <summary>
            /// The get file type.
            /// </summary>
            /// <returns>
            /// The <see cref="string"/>.
            /// </returns>
            public string GetFileType()
            {
                return this._fileInfo.Extension;
            }

            /// <summary>
            /// The open read.
            /// </summary>
            /// <returns>
            /// The <see cref="Stream"/>.
            /// </returns>
            public Stream OpenRead()
            {
                return new FileStream(this._fileInfo.FullName, FileMode.Open, FileAccess.Read);
            }

            /// <summary>
            /// The open write.
            /// </summary>
            /// <returns>
            /// The <see cref="Stream"/>.
            /// </returns>
            public Stream OpenWrite()
            {
                return new FileStream(this._fileInfo.FullName, FileMode.Open, FileAccess.ReadWrite);
            }

            #endregion
        }

        /// <summary>
        /// The file system storage folder.
        /// </summary>
        private class FileSystemStorageFolder : IStorageFolder
        {
            /// <summary>
            /// The _directory info.
            /// </summary>
            private readonly DirectoryInfo _directoryInfo;

            /// <summary>
            /// The _path.
            /// </summary>
            private readonly string _path;

            /// <summary>
            /// Initializes a new instance of the <see cref="FileSystemStorageFolder"/> class.
            /// </summary>
            /// <param name="path">
            /// The path.
            /// </param>
            /// <param name="directoryInfo">
            /// The directory info.
            /// </param>
            public FileSystemStorageFolder(string path, DirectoryInfo directoryInfo)
            {
                this._path = path;
                this._directoryInfo = directoryInfo;
            }

            /// <summary>
            /// The get directory size.
            /// </summary>
            /// <param name="directoryInfo">
            /// The directory info.
            /// </param>
            /// <returns>
            /// The <see cref="long"/>.
            /// </returns>
            private static long GetDirectorySize(DirectoryInfo directoryInfo)
            {
                long size = 0;

                var fileInfos = directoryInfo.GetFiles();
                foreach (var fileInfo in fileInfos)
                {
                    if (!IsHidden(fileInfo))
                    {
                        size += fileInfo.Length;
                    }
                }

                var directoryInfos = directoryInfo.GetDirectories();
                foreach (var dInfo in directoryInfos)
                {
                    if (!IsHidden(dInfo))
                    {
                        size += GetDirectorySize(dInfo);
                    }
                }

                return size;
            }

            #region Implementation of IStorageFolder

            /// <summary>
            /// The get path.
            /// </summary>
            /// <returns>
            /// The <see cref="string"/>.
            /// </returns>
            public string GetPath()
            {
                return this._path;
            }

            /// <summary>
            /// The get name.
            /// </summary>
            /// <returns>
            /// The <see cref="string"/>.
            /// </returns>
            public string GetName()
            {
                return this._directoryInfo.Name;
            }

            /// <summary>
            /// The get last updated.
            /// </summary>
            /// <returns>
            /// The <see cref="DateTime"/>.
            /// </returns>
            public DateTime GetLastUpdated()
            {
                return this._directoryInfo.LastWriteTime;
            }

            /// <summary>
            /// The get size.
            /// </summary>
            /// <returns>
            /// The <see cref="long"/>.
            /// </returns>
            public long GetSize()
            {
                return GetDirectorySize(this._directoryInfo);
            }

            /// <summary>
            /// The get parent.
            /// </summary>
            /// <returns>
            /// The <see cref="IStorageFolder"/>.
            /// </returns>
            /// <exception cref="ArgumentException">
            /// </exception>
            public IStorageFolder GetParent()
            {
                if (this._directoryInfo.Parent != null)
                {
                    return new FileSystemStorageFolder(Path.GetDirectoryName(this._path), this._directoryInfo.Parent);
                }

                throw new ArgumentException(
                    "Directory " + this._directoryInfo.Name + " does not have a parent directory");
            }

            #endregion
        }

        #region Implementation of IStorageProvider

        /// <summary>
        /// The get public url.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public string GetPublicUrl(string path)
        {
            return this._publicPath + path.Replace(Path.DirectorySeparatorChar, '/');
        }

        /// <summary>
        /// The get file.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="IStorageFile"/>.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public IStorageFile GetFile(string path)
        {
            if (!File.Exists(this.Map(path)))
            {
                throw new ArgumentException("File " + path + " does not exist");
            }

            return new FileSystemStorageFile(Fix(path), new FileInfo(this.Map(path)));
        }

        /// <summary>
        /// The list files.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public IEnumerable<IStorageFile> ListFiles(string path)
        {
            if (!Directory.Exists(this.Map(path)))
            {
                throw new ArgumentException("Directory " + path + " does not exist");
            }

            return
                new DirectoryInfo(this.Map(path)).GetFiles()
                    .Where(fi => !IsHidden(fi))
                    .Select<FileInfo, IStorageFile>(
                        fi => new FileSystemStorageFile(Path.Combine(Fix(path), fi.Name), fi))
                    .ToList();
        }

        /// <summary>
        /// The list folders.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public IEnumerable<IStorageFolder> ListFolders(string path)
        {
            if (!Directory.Exists(this.Map(path)))
            {
                try
                {
                    Directory.CreateDirectory(this.Map(path));
                }
                catch (Exception ex)
                {
                    throw new ArgumentException(
                        string.Format("The folder could not be created at path: {0}. {1}", path, ex));
                }
            }

            return
                new DirectoryInfo(this.Map(path)).GetDirectories()
                    .Where(di => !IsHidden(di))
                    .Select<DirectoryInfo, IStorageFolder>(
                        di => new FileSystemStorageFolder(Path.Combine(Fix(path), di.Name), di))
                    .ToList();
        }

        /// <summary>
        /// The is hidden.
        /// </summary>
        /// <param name="di">
        /// The di.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private static bool IsHidden(FileSystemInfo di)
        {
            return (di.Attributes & FileAttributes.Hidden) != 0;
        }

        /// <summary>
        /// The create folder.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void CreateFolder(string path)
        {
            if (Directory.Exists(this.Map(path)))
            {
                throw new ArgumentException("Directory " + path + " already exists");
            }

            Directory.CreateDirectory(this.Map(path));
        }

        /// <summary>
        /// The delete folder.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void DeleteFolder(string path)
        {
            if (!Directory.Exists(this.Map(path)))
            {
                throw new ArgumentException("Directory " + path + " does not exist");
            }

            Directory.Delete(this.Map(path), true);
        }

        /// <summary>
        /// The rename folder.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <param name="newPath">
        /// The new path.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void RenameFolder(string path, string newPath)
        {
            if (!Directory.Exists(this.Map(path)))
            {
                throw new ArgumentException("Directory " + path + "does not exist");
            }

            if (Directory.Exists(this.Map(newPath)))
            {
                throw new ArgumentException("Directory " + newPath + " already exists");
            }

            Directory.Move(this.Map(path), this.Map(newPath));
        }

        /// <summary>
        /// The create file.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <returns>
        /// The <see cref="IStorageFile"/>.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public IStorageFile CreateFile(string path)
        {
            if (File.Exists(this.Map(path)))
            {
                throw new ArgumentException("File " + path + " already exists");
            }

            var fileInfo = new FileInfo(this.Map(path));
            File.WriteAllBytes(this.Map(path), new byte[0]);

            return new FileSystemStorageFile(Fix(path), fileInfo);
        }

        /// <summary>
        /// The delete file.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void DeleteFile(string path)
        {
            if (!File.Exists(this.Map(path)))
            {
                throw new ArgumentException("File " + path + " does not exist");
            }

            File.Delete(this.Map(path));
        }

        /// <summary>
        /// The rename file.
        /// </summary>
        /// <param name="path">
        /// The path.
        /// </param>
        /// <param name="newPath">
        /// The new path.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void RenameFile(string path, string newPath)
        {
            if (!File.Exists(this.Map(path)))
            {
                throw new ArgumentException("File " + path + " does not exist");
            }

            if (File.Exists(this.Map(newPath)))
            {
                throw new ArgumentException("File " + newPath + " already exists");
            }

            File.Move(this.Map(path), this.Map(newPath));
        }

        #endregion
    }
}